class ParticlesGravity5 extends egret.DisplayObjectContainer {
    private particles: Array<Ball>
    private numParticles: number = 30
    private minDist: number = 100
    private springAmount: number = .001

    public constructor() {
        super()
        this.once( egret.Event.ADDED_TO_STAGE, this.onAddToStage, this)
    }

    private onAddToStage():void {
        this.particles = new Array<Ball>()
        for (let i = 0; i < this.numParticles; ++i) {
            let particle = new Ball(5, 0xFFFFFF)
            particle.x = Math.random() * this.stage.stageWidth
            particle.y = Math.random() * this.stage.stageHeight
            particle.mass = 1
            this.particles.push(particle)
            this.addChild(particle)
        }

        this.addEventListener(egret.Event.ENTER_FRAME, this.onEnterFrame, this)
    }

    private onEnterFrame() {
        for (let particle of this.particles) {
            particle.x += particle.vx
            particle.y += particle.vy
            this.checkWarp(particle)
        }

        for (let i = 0; i < this.numParticles - 1; ++i) {
            let partA = this.particles[i]
            for (let j = i + 1; j < this.numParticles; ++j) {
                let partB = this.particles[j]
                this.spring(partA, partB)
            }
        }
    }

    private spring(partA:Ball, partB:Ball) {
        let dx = partB.x - partA.x
        let dy = partB.y - partA.y
        let dist = Math.sqrt(dx * dx + dy * dy)
        if (dist < this.minDist) {
            let ax = dx * this.springAmount
            let ay = dy * this.springAmount
            partA.vx += ax
            partA.vy += ay
            partB.vx -= ax
            partB.vy -= ay
        }
    }

    // 折回
    private checkWarp(particle:Ball) {
        if (particle.x > this.stage.stageWidth) {
            particle.x = 0
        } else if (particle.x < 0) {
            particle.x = this.stage.stageWidth
        }

        if (particle.y > this.stage.stageHeight) {
            particle.y = 0
        } else if (particle.y < 0) {
            particle.y = this.stage.stageHeight
        }
    }

    private gravitate(partA:Ball, partB:Ball) {
        let dx = partB.x - partA.x
        let dy = partB.y - partA.y
        let distSQ = dx * dx + dy * dy
        let dist = Math.sqrt(distSQ)
        let force = partA.mass * partB.mass / distSQ
        let ax = force * dx / dist //  dx / dist == cos(angle)
        let ay = force * dy / dist //  dy / dist == sin(angle)
        partA.vx += ax / partA.mass
        partA.vy += ay / partA.mass
        partB.vx -= ax / partB.mass
        partB.vy -= ay / partB.mass
    }

    private checkCollection(ball0: Ball, ball1: Ball) {
        let dx = ball1.x - ball0.x
        let dy = ball1.y - ball0.y
        let dist = Math.sqrt(dx * dx + dy * dy)

        if (dist < ball0.radius + ball1.radius) {

            let angle = Math.atan2(dy, dx)
            let sin = Math.sin(angle)
            let cos = Math.cos(angle)

            // 旋转ball0的位置，是按照ball0进行旋转的，那么自己相对于自己的相对位置是0
            let pos0 = this.rotate(0, 0, sin, cos, true)

            // 旋转ball1的位置，已知相对位置分别为dx, dy，那么直接进行旋转操作
            let pos1 = this.rotate(dx, dy, sin, cos, true)

            // 旋转ball0的速度
            let vel0 = this.rotate(ball0.vx, ball0.vy, sin, cos, true)

            // 旋转ball1的速度
            let vel1 = this.rotate(ball1.vx, ball1.vy, sin, cos, true)

            // 碰撞反应
            let vxTotal = vel0.x - vel1.x
            vel0.x = ((ball0.mass - ball1.mass) * vel0.x + 2 * ball1.mass * vel1.x) / (ball0.mass + ball1.mass)
            vel1.x = vxTotal + vel0.x

            // 更新位置
            let absV = Math.abs(vel0.x) + Math.abs(vel1.x)
            let overlap = (ball0.radius + ball1.radius) - Math.abs(pos0.x - pos1.x)
            pos0.x += vel0.x / absV * overlap
            pos1.x += vel1.x / absV * overlap

            // 把位置旋转回去
            let pos0F = this.rotate(pos0.x, pos0.y, sin, cos, false)
            let pos1F = this.rotate(pos1.x, pos1.y, sin, cos, false)
            
            // 将位置调整到实际的屏幕位置
            ball1.x = ball0.x + pos1F.x
            ball1.y = ball0.y + pos1F.y
            ball0.x = ball0.x + pos0F.x
            ball0.y = ball0.y + pos0F.y

            // 将速度旋转回去
            let vel0F = this.rotate(vel0.x, vel0.y, sin, cos, false)
            let vel1F = this.rotate(vel1.x, vel1.y, sin, cos, false)
            ball0.vx = vel0F.x
            ball0.vy = vel0F.y
            ball1.vx = vel1F.x
            ball1.vy = vel1F.y
        }
    }

    private rotate(x: number, y: number, sin: number, cos: number, reverse: boolean): { x: number; y: number; } {
        let result = { x: 0, y: 0 }
        if (reverse) {
            result.x = x * cos + y * sin
            result.y = y * cos - x * sin
        } else {
            result.x = x * cos - y * sin
            result.y = y * cos + x * sin
        }
        return result
    }
}